/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#ifndef SHARED_DB_SQLSTORAGEIMPL_H_
#define SHARED_DB_SQLSTORAGEIMPL_H_

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif

#include "Shared/Log/Log.h"
#include "Shared/Util/ProgressBar.h"
#include "Shared/DBC/DBCFileLoader.h"
using Shared::Util::BarGoLink;
using Shared::DBC::FieldFormat;


namespace Shared {
namespace DB {

    template<class DerivedLoader, class StorageClass>
    template<class S, class D>
    void SQLStorageLoaderBase<DerivedLoader, StorageClass>::convert(std::uint32_t /*field_pos*/, S src, D& dst)
    {
        dst = D(src);
    }


    template<class DerivedLoader, class StorageClass>
    void SQLStorageLoaderBase<DerivedLoader, StorageClass>::convert_str_to_str(std::uint32_t /*field_pos*/, char const* src, char*& dst)
    {
        if (!src)
        {
            dst = new char[1];
            *dst = 0;
        }
        else
        {
            std::uint32_t l = strlen(src) + 1;
            dst = new char[l];
            memcpy(dst, src, l);
        }
    }


    template<class DerivedLoader, class StorageClass>
    template<class S>
    void SQLStorageLoaderBase<DerivedLoader, StorageClass>::convert_to_str(std::uint32_t /*field_pos*/, S /*src*/, char*& dst)
    {
        dst = new char[1];
        *dst = 0;
    }


    template<class DerivedLoader, class StorageClass>
    template<class D>
    void SQLStorageLoaderBase<DerivedLoader, StorageClass>::convert_from_str(std::uint32_t /*field_pos*/, char const* /*src*/, D& dst)
    {
        dst = 0;
    }


    template<class DerivedLoader, class StorageClass>
    template<class S, class D>
    void SQLStorageLoaderBase<DerivedLoader, StorageClass>::default_fill(std::uint32_t /*field_pos*/, S src, D& dst)
    {
        dst = D(src);
    }


    template<class DerivedLoader, class StorageClass>
    void SQLStorageLoaderBase<DerivedLoader, StorageClass>::default_fill_to_str(std::uint32_t /*field_pos*/, char const* /*src*/, char*& dst)
    {
        dst = new char[1];
        *dst = 0;
    }


    template<class DerivedLoader, class StorageClass>
    template<class V>
    void SQLStorageLoaderBase<DerivedLoader, StorageClass>::storeValue(V value, StorageClass& store, char* p, std::uint32_t x, std::uint32_t& offset)
    {
        DerivedLoader* subclass = (static_cast<DerivedLoader*>(this));
        switch (store.getDstFormat(x))
        {
            case Shared::DBC::DBC_FF_LOGIC:
                subclass->convert(x, value, *((bool*)(&p[offset])));
                offset += sizeof(bool);
                break;
            case Shared::DBC::DBC_FF_BYTE:
                subclass->convert(x, value, *((char*)(&p[offset])));
                offset += sizeof(char);
                break;
            case Shared::DBC::DBC_FF_INT:
                subclass->convert(x, value, *((std::uint32_t*)(&p[offset])));
                offset += sizeof(std::uint32_t);
                break;
            case Shared::DBC::DBC_FF_FLOAT:
                subclass->convert(x, value, *((float*)(&p[offset])));
                offset += sizeof(float);
                break;
            case Shared::DBC::DBC_FF_STRING:
                subclass->convert_to_str(x, value, *((char**)(&p[offset])));
                offset += sizeof(char*);
                break;
            case Shared::DBC::DBC_FF_NA:
                subclass->default_fill(x, value, *((std::uint32_t*)(&p[offset])));
                offset += sizeof(std::uint32_t);
                break;
            case Shared::DBC::DBC_FF_NA_BYTE:
                subclass->default_fill(x, value, *((char*)(&p[offset])));
                offset += sizeof(char);
                break;
            case Shared::DBC::DBC_FF_NA_FLOAT:
                subclass->default_fill(x, value, *((float*)(&p[offset])));
                offset += sizeof(float);
                break;
            case Shared::DBC::DBC_FF_IND:
            case Shared::DBC::DBC_FF_SORT:
                assert(false && "SQL storage does not have sort field types");
                break;
            default:
                assert(false && "unknown format character");
                break;
        }
    }


    template<class DerivedLoader, class StorageClass>
    void SQLStorageLoaderBase<DerivedLoader, StorageClass>::storeValue(char const* value, StorageClass& store, char* p, std::uint32_t x, std::uint32_t& offset)
    {
        DerivedLoader* subclass = (static_cast<DerivedLoader*>(this));
        switch (store.getDstFormat(x))
        {
            case Shared::DBC::DBC_FF_LOGIC:
                subclass->convert_from_str(x, value, *((bool*)(&p[offset])));
                offset += sizeof(bool);
                break;
            case Shared::DBC::DBC_FF_BYTE:
                subclass->convert_from_str(x, value, *((char*)(&p[offset])));
                offset += sizeof(char);
                break;
            case Shared::DBC::DBC_FF_INT:
                subclass->convert_from_str(x, value, *((std::uint32_t*)(&p[offset])));
                offset += sizeof(std::uint32_t);
                break;
            case Shared::DBC::DBC_FF_FLOAT:
                subclass->convert_from_str(x, value, *((float*)(&p[offset])));
                offset += sizeof(float);
                break;
            case Shared::DBC::DBC_FF_STRING:
                subclass->convert_str_to_str(x, value, *((char**)(&p[offset])));
                offset += sizeof(char*);
                break;
            case Shared::DBC::DBC_FF_NA_POINTER:
                subclass->default_fill_to_str(x, value, *((char**)(&p[offset])));
                offset += sizeof(char*);
                break;
            case Shared::DBC::DBC_FF_IND:
            case Shared::DBC::DBC_FF_SORT:
                assert(false && "SQL storage does not have sort field types");
                break;
            default:
                assert(false && "unknown format character");
                break;
        }
    }


    ///////////////////////////////////////////////////////////////////////////
    // Load
    template<class DerivedLoader, class StorageClass>
    bool SQLStorageLoaderBase<DerivedLoader, StorageClass>::load(Shared::Database &db, StorageClass& store, bool error_at_empty /*= true*/)
    {
        Field* fields = NULL;
        QueryResultPtr result(db.pquery("SELECT MAX(%s) FROM %s", store.entryFieldName(), store.getTableName()));
        if (!result.get())
        {
            sharedLog.outError("Error loading %s table (not exist?)\n", store.getTableName());
            return false;
        }

        std::uint32_t maxRecordId = (*result)[0].toUInt32() + 1;
        std::uint32_t recordCount = 0;
        std::uint32_t recordsize = 0;

        result.reset(db.pquery("SELECT COUNT(*) FROM %s", store.getTableName()));
        if (result.get())
        {
            fields = result->fetch();
            recordCount = fields[0].toUInt32();
        }

        result.reset(db.pquery("SELECT * FROM %s", store.getTableName()));

        if (!result.get())
        {
            if (error_at_empty) { 
                sharedLog.outError("%s table is empty!\n", store.getTableName()); 
            }
            else { 
                sharedLog.outString("%s table is empty!\n", store.getTableName()); 
            }

            recordCount = 0;
            return error_at_empty;
        }

        if (store.getSrcFieldCount() != result->getFieldCount())
        {
            recordCount = 0;
            sharedLog.outError("Error in %s table.Perhaps the table structure was changed. There should be %d fields in the table.\n", store.getTableName(), store.getSrcFieldCount());
            return false;
        }

        // get struct size
        std::uint32_t offset = 0;
        for (std::uint32_t x = 0; x < store.getDstFieldCount(); ++x)
        {
            switch (store.getDstFormat(x))
            {
            case Shared::DBC::DBC_FF_LOGIC:
                recordsize += sizeof(bool);   break;
            case Shared::DBC::DBC_FF_BYTE:
                recordsize += sizeof(char);   break;
            case Shared::DBC::DBC_FF_INT:
                recordsize += sizeof(std::uint32_t); break;
            case Shared::DBC::DBC_FF_FLOAT:
                recordsize += sizeof(float);  break;
            case Shared::DBC::DBC_FF_STRING:
                recordsize += sizeof(char*);  break;
            case Shared::DBC::DBC_FF_NA:
                recordsize += sizeof(std::uint32_t); break;
            case Shared::DBC::DBC_FF_NA_BYTE:
                recordsize += sizeof(char);   break;
            case Shared::DBC::DBC_FF_NA_FLOAT:
                recordsize += sizeof(float);  break;
            case Shared::DBC::DBC_FF_NA_POINTER:
                recordsize += sizeof(char*);  break;
            case Shared::DBC::DBC_FF_IND:
            case Shared::DBC::DBC_FF_SORT:
                assert(false && "SQL storage not have sort field types");
                break;
            default:
                assert(false && "unknown format character");
                break;
            }
        }

        // Prepare data storage and lookup storage
        store.prepareToLoad(maxRecordId, recordCount, recordsize);

        BarGoLink bar(recordCount);
        do
        {
            fields = result->fetch();
            bar.step();

            char* record = store.createRecord(fields[0].toUInt32());
            offset = 0;

            // dependend on dest-size
            // iterate two indexes: x over dest, y over source
            //                      y++ If and only If x != FT_NA*
            //                      x++ If and only If a value is stored
            for (std::uint32_t x = 0, y = 0; x < store.getDstFieldCount();)
            {
                switch (store.getDstFormat(x))
                {
                    // For default fill continue and do not increase y
                case Shared::DBC::DBC_FF_NA:         storeValue((std::uint32_t)0, store, record, x, offset);         ++x; continue;
                case Shared::DBC::DBC_FF_NA_BYTE:    storeValue((char)0, store, record, x, offset);           ++x; continue;
                case Shared::DBC::DBC_FF_NA_FLOAT:   storeValue((float)0.0f, store, record, x, offset);       ++x; continue;
                case Shared::DBC::DBC_FF_NA_POINTER: storeValue((char const*)NULL, store, record, x, offset); ++x; continue;
                default:
                    break;
                }

                // It is required that the input has at least as many columns set as the output requires
                if (y >= store.getSrcFieldCount())
                { assert(false && "SQL storage has too few columns!"); }

                switch (store.getSrcFormat(y))
                {
                case Shared::DBC::DBC_FF_LOGIC:  storeValue((bool)(fields[y].toUInt32() > 0), store, record, x, offset);  ++x; break;
                case Shared::DBC::DBC_FF_BYTE:   storeValue((char)fields[y].toUInt8(), store, record, x, offset);         ++x; break;
                case Shared::DBC::DBC_FF_INT:    storeValue((std::uint32_t)fields[y].toUInt32(), store, record, x, offset);      ++x; break;
                case Shared::DBC::DBC_FF_FLOAT:  storeValue((float)fields[y].toFloat(), store, record, x, offset);        ++x; break;
                case Shared::DBC::DBC_FF_STRING: storeValue((char const*)fields[y].c_str(), store, record, x, offset); ++x; break;
                case Shared::DBC::DBC_FF_NA:
                case Shared::DBC::DBC_FF_NA_BYTE:
                case Shared::DBC::DBC_FF_NA_FLOAT:
                    // Do Not increase x
                    break;
                case Shared::DBC::DBC_FF_IND:
                case Shared::DBC::DBC_FF_SORT:
                case Shared::DBC::DBC_FF_NA_POINTER:
                    assert(false && "SQL storage not have sort or pointer field types");
                    break;
                default:
                    assert(false && "unknown format character");
                }
                ++y;
            }
        }
        while (result->nextRow());

        return true;
    }
}}

#endif // SHARED_DB_SQLSTORAGEIMPL_H_
